package com.itealabs.postjson.ext;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

import com.itealabs.postjson.JsonCall;
import com.itealabs.postjson.JsonCallWaiter;
import com.itealabs.postjson.JsonCallWaiterLocator;
import com.itealabs.postjson.JsonRequest;
import com.itealabs.postjson.JsonRequestWaiter;
import com.itealabs.postjson.JsonResponse;
import com.itealabs.postjson.JsonReturn;
import com.itealabs.postjson.JsonReturn.State;

/**
 * 
 * @version $Id: ConcurrentJsonRequestWaiter.java 21 2010-05-06 14:34:52Z iteahere $
 * @author itea
 * 2010-4-20
 */
public class ConcurrentJsonRequestWaiter implements JsonRequestWaiter {

	private JsonCallWaiterLocator jsonCallWaiterLocator;
	
	private ExecutorService executorService;
	
	private int maxAssistantPerRequest = 1;
	
	static JsonCallWaiter notFoundCallWaiter = new JsonCallWaiter() {
		public JsonReturn service(JsonCall jsonCall,
				Map<String, Object> ctx) {
			return new JsonReturn(State.ERROR, 
					new NoSuchMethodException("NOT FOUND: "+ jsonCall.getMethod()));
		}
	};
	
	protected class Worker implements Callable<List<JsonReturn>> {

		private Queue<JsonCall> callsQueue;
		
		Map<String, Object> ctx;
		
		public Worker(Queue<JsonCall> callsQueue, Map<String, Object> ctx) {
			this.callsQueue = callsQueue;
			this.ctx = ctx;
		}

		public List<JsonReturn> call() throws Exception {
			List<JsonReturn> returns = new ArrayList<JsonReturn>();
			JsonCall call = null;
			while((call = callsQueue.poll()) != null)
				returns.add(callAndGetReturn(call, ctx));
			return returns;
		}
		
		private JsonReturn callAndGetReturn(JsonCall jc, Map<String, Object> ctx) {
			Long idx = jc.getIdx();
			JsonReturn rt = null;
			try {
				rt = getCallWaiter(jc.getMethod()).service(jc, ctx);
				rt.setIdx(idx);
				return rt;
			} catch (Exception e) {
				//e.printStackTrace(); // FIXME remove
				return new JsonReturn(idx, State.ERROR, e);
			}
		}
		
		protected JsonCallWaiter getCallWaiter(String method) {
			JsonCallWaiter callWaiter = jsonCallWaiterLocator.getCallWaiter(method);
			return callWaiter == null ? notFoundCallWaiter : callWaiter;
		}
		
	}

	protected List<Future<List<JsonReturn>>> submitCalls(List<JsonCall> calls,
			Queue<JsonCall> callsQueue, Map<String, Object> ctx) {
		int assNum = Math.min(calls.size()/2, maxAssistantPerRequest);
		List<Future<List<JsonReturn>>> futures =
			new ArrayList<Future<List<JsonReturn>>>(assNum);
		try {
			for(int i=0, j= assNum; i< j; i++)
				futures.add(executorService.submit(new Worker(callsQueue, ctx)));
		} catch (RejectedExecutionException e) {}
		return futures;
	}
	
	public void service(JsonRequest jsonReq, JsonResponse jsonResp,
			Map<String, Object> ctx) {

		Queue<JsonCall> callsQueue = new ConcurrentLinkedQueue<JsonCall>(jsonReq.getCalls());
		List<Future<List<JsonReturn>>> futures = submitCalls(jsonReq.getCalls(), callsQueue, ctx);
		Worker mainWorker = new Worker(callsQueue, ctx);
		List<JsonReturn> returns = new ArrayList<JsonReturn>(jsonReq.getCalls().size());
		try {
			returns.addAll(mainWorker.call());
			for(Future<List<JsonReturn>> f : futures)
				returns.addAll(f.get());
		} catch (Exception e) {}
		jsonResp.setReturns(returns);
	}

	public JsonCallWaiterLocator getJsonCallWaiterLocator() {
		return jsonCallWaiterLocator;
	}

	public void setJsonCallWaiterLocator(JsonCallWaiterLocator jsonCallWaiterLocator) {
		this.jsonCallWaiterLocator = jsonCallWaiterLocator;
	}

	public ExecutorService getExecutorService() {
		return executorService;
	}

	public void setExecutorService(ExecutorService executorService) {
		this.executorService = executorService;
	}

	public int getMaxAssistantPerRequest() {
		return maxAssistantPerRequest;
	}

	public void setMaxAssistantPerRequest(int maxAssistantPerRequest) {
		this.maxAssistantPerRequest = maxAssistantPerRequest;
	}
}
